package com.kate.mall.bff.wx.service;

import cn.hutool.core.map.MapUtil;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kate.mall.bff.wx.feign.*;
import com.kate.mall.bff.wx.vo.HomeBannerVo;
import com.kate.mall.bff.wx.vo.HomeChannelVo;
import com.kate.mall.bff.wx.vo.HomeFloorGoodsListVo;
import com.kate.mall.bff.wx.vo.HomeTopicVo;
import com.kate.mall.bff.wx.vo.brand.HomeBrandVo;
import com.kate.mall.bff.wx.vo.coupon.CouponVo;
import com.kate.mall.bff.wx.vo.goods.FloorGoodsVo;
import com.kate.mall.bff.wx.vo.goods.NewOrHotGoodsVo;
import com.kate.mall.bff.wx.vo.promotion.GroupOnRulesVo;
import com.kate.mall.common.exception.NDException;
import com.kate.mall.common.utils.ListMap2ListVoUtils2;
import com.kate.mall.common.utils.R;
import com.kate.mall.common.utils.ThreadPoolUtils;
import com.kate.mall.common.vo.messsage.MessageRowsVo;
import lombok.AllArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.json.JsonArray;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * @author 新梦想.陈超
 * @version 2021.2
 * @Description: {TODO}
 * @date 2022/12/16 下午2:11
 */

@Service
@AllArgsConstructor
public class HomeService3 {
    private ProductFeignClient productFeignClient;
    private ListMap2ListVoUtils2 listMap2ListVoUtils;
    private CouponFeignClient couponFeignClient;
    private SnmFeignClient snmFeignClient;
    private MallFeignClient mallFeignClient;
    private PromotionFeignClient promotionFeignClient;
    private GrouponService grouponService;

    /**
     * @return
     * @Cacheable 作用就是将方法的返回值，以指定key的方式保存redis中，以后再调用该方法，先从redis中查询数据
     * 如果有，则直接返回，如果没有，则执行当前方法，远程调用，进行数据库的查询
     * cacheNames   缓存名
     * key：        缓存键
     * unless       指定方法返回值满足条件，则不缓存
     * #result['data']?.size()  就是方法返回值的data为key,对应的value的size为0 ，则不缓存返回值
     * <p>
     * <p>
     * 1.如何防止突刺流量访问？
     * 首页缓存预热：上线前或上线时，将首页数据查询出来，进行缓存！！
     * 系统启动任务提：CommandLineRunner 和 ApplicationRunner,
     * 2.如果缓存失效，那一刹那，突然高并发访问怎么办？
     * 1)上线时间：晚上或凌晨2点
     * 2）缓存失效起始时间也是从凌晨2点开始，失效时间以天单位。
     * 3.首页缓存与数据库数据的一致性如何处理？
     * 1）设置首页缓存时间3天/一周
     * 2）广告、新品、热品的更新必须提前3天/一周
     * <p>
     * 4.缓存key的设计
     * cacheNames:服务名
     * key:业务:表:唯一标示
     */
    @Cacheable(cacheNames = "wx-bff", key = "'h:i'", unless = "#result['data']?.size()==0")
    public R list() {
        Map resultMap = new HashMap();
        CompletableFuture<R> completableFutureList = new CompletableFuture<>();
        ThreadPoolExecutor pool = ThreadPoolUtils.instance(true);
        try {
            //解决：------------------------------------------------------------------------------------------------------------
            //获取旧线程的RequestAttributes
            //RequestContextHolder底层使用的是ThreadLocal
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
            //解决：----------------------------------------------------------------------------------------------------------
            //TODO 1.新品列表   page=1  limit=6
            CompletableFuture<R> newGoodsFuture = generatorFeignFuture(pool, requestAttributes,
                    () -> productFeignClient.queryNewOrHotGoodsList(
                    generatorParams(
                            new String[]{"limit", "isNew", "sort", "order"},
                            new Object[]{"6", 1, "sort_order", "asc"})));
            //TODO 2.优惠券列表 page=1  limit=4
            CompletableFuture<R> couponListFuture = generatorFeignFuture(pool, requestAttributes, () -> couponFeignClient.selectCouponList(generatorParams(
                    new String[]{"limit", "sort", "order"},
                    new Object[]{"4", "add_time", "asc"})));
            //TODO 3.分类列表 page=1  limit=10
            CompletableFuture<R> channelListFuture = generatorFeignFuture(pool, requestAttributes, () -> productFeignClient.queryCategoryList(
                    generatorParams(new String[]{"limit"},
                            new Object[]{"10"})));
            //TODO 4.团购专区列表 page=1  limit=3
            CompletableFuture<R> groupOnListFuture = generatorFeignFuture(pool, requestAttributes, () -> grouponService.grouponRules(
                    generatorParams(new String[]{"limit", "sort", "order"},
                            new Object[]{"3", "add_time", "desc"})));
            //TODO 5.轮播图列表  page=1  limit=5
            CompletableFuture<R> bannerListFuture = generatorFeignFuture(pool, requestAttributes, () -> promotionFeignClient.bannerList(
                    generatorParams(new String[]{"limit", "sort", "order"},
                            new Object[]{"5", "add_time", "desc"})));
            //TODO 6.品牌专区   page=1  limit=4
            CompletableFuture<R> brandListFuture = generatorFeignFuture(pool, requestAttributes, () -> productFeignClient.queryBrandList(
                    generatorParams(new String[]{"limit", "sort", "order"},
                            new Object[]{"4", "add_time", "desc"})));
            //TODO 7.热卖商品列表 page=1  limit=6
            CompletableFuture<R> hotGoodsFuture = generatorFeignFuture(pool, requestAttributes, () -> productFeignClient.queryNewOrHotGoodsList(
                    generatorParams(new String[]{"limit", "isHot", "sort", "order"},
                            new Object[]{"6", 1, "sort_order", "asc"})));
            //TODO 8.专题列表   page=1  limit=4
            CompletableFuture<R> topicGoodsFuture = generatorFeignFuture(pool, requestAttributes, () -> mallFeignClient.topicGoodsList(
                    generatorParams(new String[]{"limit", "sort", "order"},
                            new Object[]{"4",  "sort_order", "desc"})));
            //TODO 9.楼层商品列表 page=1  limit=4
            CompletableFuture<R> floorGoodsListFuture = generatorFeignFuture(pool, requestAttributes, () ->
                    productFeignClient.queryFloorGoodsList(1, 4));
            //TODO 10.模板消息
            CompletableFuture<R> messageFuture = generatorFeignFuture(pool, requestAttributes, () -> snmFeignClient.refreshMessage());

            parseFuture(newGoodsFuture, resultMap, "newGoodsList", NewOrHotGoodsVo.class);
            parseFuture(hotGoodsFuture, resultMap, "hotGoodsList", NewOrHotGoodsVo.class);
            parseFutureMsg(messageFuture, resultMap, "messageRows", MessageRowsVo.class);
            parseFuture(couponListFuture, resultMap, "couponList", CouponVo.class);
            parseFuture(channelListFuture, resultMap, "channel", HomeChannelVo.class);
            parseFutureFloorGoodsList(floorGoodsListFuture, resultMap, "floorGoodsList", HomeFloorGoodsListVo.class);
            parseFuture(topicGoodsFuture, resultMap, "topicList", HomeTopicVo.class);
            parseFuture(groupOnListFuture, resultMap, "grouponList", GroupOnRulesVo.class);
            parseFuture(bannerListFuture,resultMap,"banner", HomeBannerVo.class);
            parseFuture(brandListFuture, resultMap, "brandList", HomeBrandVo.class);

        } finally {
            pool.shutdown();
        }
        return R.ok().put("data", resultMap);
    }

    private CompletableFuture<R> generatorFeignFuture(ThreadPoolExecutor pool, RequestAttributes requestAttributes, Supplier<R> supplier) {
        CompletableFuture<R> couponListFuture = CompletableFuture.supplyAsync(() -> {
            //解决：-----------------------------------------------------------------------------------------------------
            //将旧线程中的RequestAttributes，设置到线程中的RequestContextHolder里
            RequestContextHolder.setRequestAttributes(requestAttributes);
            //解决：-----------------------------------------------------------------------------------------------------
            return supplier.get();
        }, pool);
        return couponListFuture;
    }

    public void parseFutureFloorGoodsList(CompletableFuture<R> future, Map resultMap, String responseKey, Class voClazz) {
        R listR = null;
        try {
            listR = future.get();
        } catch (Exception e) {
            throw new NDException("首页接口请求数据异常....");
        }
        if (listR == null) return;
        List<Map> mapList = MapUtil.get(MapUtil.get(listR, "page", Map.class), "list", List.class);
        List<HomeFloorGoodsListVo> voList = listMap2ListVoUtils.<HomeFloorGoodsListVo>listMap2ListVo(mapList, voClazz);
        final List<HomeFloorGoodsListVo> floorVoList = voList.stream().map(homeFloorGoodsListVo -> {
            final List<Map> subMapList = mapList.stream().filter(floor -> floor.get("name").
                    equals(homeFloorGoodsListVo.getName())).collect(Collectors.toList());
            List<Map> floorGoodsList = (List<Map>) subMapList.get(0).get("goodsList");
            List<FloorGoodsVo> floorGoodsVoList = listMap2ListVoUtils.<FloorGoodsVo>listMap2ListVo(floorGoodsList, FloorGoodsVo.class);
            homeFloorGoodsListVo.setGoodsList(floorGoodsVoList);
            return homeFloorGoodsListVo;
        }).collect(Collectors.toList());
        resultMap.put(responseKey, floorVoList);
    }

    public void parseFutureMsg(CompletableFuture<R> messageFuture, Map resultMap, String responseKey, Class<MessageRowsVo> voClazz) {
        R listR = null;
        try {
            listR = messageFuture.get();
        } catch (Exception e) {
            e.printStackTrace();
            throw new NDException("首页消息请求数据异常....");
        }
        if (listR == null) return;
        MessageRowsVo messageRowsVo = null;
        try {
            messageRowsVo = voClazz.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        messageRowsVo.setLastRows((Integer) listR.get("lastRows"));
        messageRowsVo.setUnreadRows((Integer) listR.get("unreadRows"));
        resultMap.put(responseKey, messageRowsVo);
    }

    private void parseFuture(CompletableFuture<R> future, Map resultMap, String responseKey, Class voClazz) {
        R listR = null;
        try {
            listR = future.get();
        } catch (Exception e) {
            throw new NDException("首页接口请求数据异常....");
        }
        if (listR == null) return;
        List<Map> mapList = MapUtil.get(MapUtil.get(listR, "page", Map.class), "list", List.class);
        //裁剪字段两种方案：一、自己写工具类(框架)  二 、mapStruct框架
        //List<map>====>list<vo>
        List VoList = listMap2ListVoUtils.listMap2ListVo(mapList, voClazz);
        resultMap.put(responseKey, VoList);
    }

    public Map<String, Object> generatorParams(String[] keys, Object[] values) {
        ConcurrentHashMap<String, Object> params = MapUtil.newConcurrentHashMap();
        if (keys.length != values.length) {
            throw new RuntimeException("键值数量不匹配，参数错误");
        }
        for (int i = 0; i < keys.length; i++) {
            params.put(keys[i], values[i]);
        }
        return params;
    }
}
